package repository

import (
	"context"
	"errors"
	"webook/interactive/domain"
	"webook/interactive/repository/cache"
	"webook/interactive/repository/dao"
	"webook/pkg/logger"
)

type InteractiveRepository interface {
	IncrReadCnt(ctx context.Context, biz string, bizId int64) error
	// BatchIncrReadCnt 这里调用者要保证 bizs 和 bizIds 长度一样
	BatchIncrReadCnt(ctx context.Context, bizs []string, bizIds []int64) error
	IncrLike(ctx context.Context, biz string, bizId, uid int64) error
	DecrLike(ctx context.Context, biz string, bizId, uid int64) error
	AddCollectionItem(ctx context.Context, biz string, bizId, cid int64, uid int64) error
	// Get 获取某个具体 biz 的各种阅读点赞收藏信息
	Get(ctx context.Context, biz string, bizId int64) (domain.Interactive, error)
	// Liked 某个用户是否对该 biz 点赞
	Liked(ctx context.Context, biz string, id int64, uid int64) (bool, error)
	// Collected 某个用户是否对该 biz 收藏
	Collected(ctx context.Context, biz string, id int64, uid int64) (bool, error)
	GetByIds(ctx context.Context, biz string, ids []int64) ([]domain.Interactive, error)
}

type CachedInteractiveRepository struct {
	cache cache.InteractiveCache
	dao   dao.InteractiveDAO
	l     logger.LoggerV1
}

func NewCachedInteractiveRepository(cache cache.InteractiveCache,
	dao dao.InteractiveDAO, l logger.LoggerV1) InteractiveRepository {
	return &CachedInteractiveRepository{
		cache: cache,
		dao:   dao,
		l:     l,
	}
}

func (repo *CachedInteractiveRepository) IncrReadCnt(ctx context.Context, biz string, bizId int64) error {
	err := repo.dao.IncrReadCnt(ctx, biz, bizId)
	if err != nil {
		return err
	}
	// 会有部分失败引起的不一致的问题，但其实不需要解决，
	// 因为阅读数不准确完全没有问题
	return repo.cache.IncrReadCntIfPresent(ctx, biz, bizId)
}

func (repo *CachedInteractiveRepository) BatchIncrReadCnt(ctx context.Context, bizs []string, bizIds []int64) error {
	return repo.dao.BatchIncrReadCnt(ctx, bizs, bizIds)
}

func (repo *CachedInteractiveRepository) IncrLike(ctx context.Context, biz string, bizId, uid int64) error {
	err := repo.dao.InsertLikeInfo(ctx, biz, bizId, uid)
	if err != nil {
		return err
	}
	return repo.cache.IncrLikeCntIfPresent(ctx, biz, bizId)
}

func (repo *CachedInteractiveRepository) DecrLike(ctx context.Context, biz string, bizId, uid int64) error {
	err := repo.dao.DeleteLikeInfo(ctx, biz, bizId, uid)
	if err != nil {
		return err
	}
	return repo.cache.DecrLikeCntIfPresent(ctx, biz, bizId)
}

func (repo *CachedInteractiveRepository) AddCollectionItem(ctx context.Context, biz string, bizId, cid int64, uid int64) error {
	err := repo.dao.InsertCollectionBiz(ctx, dao.UserCollectionBiz{
		Biz:   biz,
		BizId: bizId,
		Cid:   cid,
		Uid:   uid,
	})
	if err != nil {
		return err
	}
	return repo.cache.IncrCollectCntIfPresent(ctx, biz, bizId)
}

func (repo *CachedInteractiveRepository) Get(ctx context.Context, biz string, bizId int64) (domain.Interactive, error) {
	res, err := repo.cache.Get(ctx, biz, bizId)
	if err == nil {
		return res, nil
	}
	intr, err := repo.dao.Get(ctx, biz, bizId)
	if err == nil || errors.Is(err, dao.ErrRecordNotFound) {
		res = repo.toDomain(intr)
		if er := repo.cache.Set(ctx, biz, bizId, res); err != nil {
			repo.l.Error("回写缓存失败", logger.Int64("bizId", bizId),
				logger.Int64("bizId", bizId), logger.String("biz", biz),
				logger.Error(er))
		}
		return res, nil
	}
	return domain.Interactive{}, err
}

func (repo *CachedInteractiveRepository) Liked(ctx context.Context, biz string, bizId int64, uid int64) (bool, error) {
	_, err := repo.dao.GetLikeInfo(ctx, biz, bizId, uid)
	switch err {
	case nil:
		return true, nil
	case dao.ErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (repo *CachedInteractiveRepository) Collected(ctx context.Context, biz string, bizId int64, uid int64) (bool, error) {
	_, err := repo.dao.GetCollectionInfo(ctx, biz, bizId, uid)
	switch err {
	case nil:
		return true, nil
	case dao.ErrRecordNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (repo *CachedInteractiveRepository) GetByIds(ctx context.Context, biz string, ids []int64) ([]domain.Interactive, error) {
	res, err := repo.dao.GetByIds(ctx, biz, ids)
	if err != nil {
		return []domain.Interactive{}, err
	}
	intrs := make([]domain.Interactive, len(res))
	for i, intr := range res {
		intrs[i] = repo.toDomain(intr)
	}
	return intrs, nil
}

func (repo *CachedInteractiveRepository) toDomain(intr dao.Interactive) domain.Interactive {
	return domain.Interactive{
		Biz:        intr.Biz,
		BizId:      intr.BizId,
		LikeCnt:    intr.LikeCnt,
		CollectCnt: intr.CollectCnt,
		ReadCnt:    intr.ReadCnt,
	}
}
